package com.sunl.mall.common.util;


import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 获取配置
 */
public final class SpringBootPropertyUtil {
    private static int springBootVersion = 2;

    static {
        try {
            // >= spring boot 2.x
            Class.forName("org.springframework.boot.context.properties.bind.Binder");
        } catch (Exception e) {
            // spring boot 1.x
            springBootVersion = 1;
        }
    }

    public static <T> T getProperties(Environment environment, String prefix, Class<T> targetClass) throws Exception {
        if (springBootVersion == 1) {
            return v1(environment, prefix, targetClass);
        }
        return v2(environment, prefix, targetClass);
    }

    @SuppressWarnings("unchecked")
    private static <T> T v1(Environment environment, String prefix, Class<T> targetClass) throws Exception {
        // 获取 RelaxedPropertyResolver 类
        Class<?> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");

        // 获取 RelaxedPropertyResolver 类中的指定构造方法、方法 Method
        Constructor<?> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);
        Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);

        // 通过构造方法创建 RelaxedPropertyResolver 对象
        Object resolver = resolverConstructor.newInstance(environment);

        // 通过 RelaxedPropertyResolver 类中的 getSubProperties 方法获取配置
        Map<String, Object> properties = (Map<String, Object>) getSubPropertiesMethod.invoke(resolver, "");
        // 存储配置的容器
        T target = targetClass.getDeclaredConstructor().newInstance();

        // 获取 RelaxedDataBinder 类
        Class<?> binderClass = Class.forName("org.springframework.boot.bind.RelaxedDataBinder");
        // 获取 RelaxedDataBinder 类中的指定构造方法、方法 Method
        Constructor<?> binderConstructor = binderClass.getDeclaredConstructor(Object.class, String.class);
        Method bindMethod = binderClass.getMethod("bind", PropertyValues.class);
        // 通过构造方法创建 RelaxedDataBinder 对象
        Object binder = binderConstructor.newInstance(target, prefix);
        // 通过 RelaxedDataBinder 类中的 bind 方法绑定配置
        bindMethod.invoke(binder, new MutablePropertyValues(properties));
        return target;
    }

    /**
     * 通过binder将env中的配置绑定到指定类型上（使用反射主要解决不同springboot版本下类不存在的问题）
     * <p>
     * 执行过程：
     * Binder binder = Binder.get(environment);
     * BindResult<T> bind = binder.bind(prefix, targetClass);
     * return bind.get();
     *
     * @param environment 环境
     * @param prefix      匹配的配置前缀
     * @param targetClass 容器(存储获取到的配置)
     * @return
     * @throws Exception 反射异常
     */
    @SuppressWarnings("unchecked")
    private static <T> T v2(Environment environment, String prefix, Class<T> targetClass) throws Exception {
        // 获取 Binder 类
        Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");

        // 获取到 Binder 中 get 和 bind 方法的 Method 对象
        Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);
        Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);

        // 通过执行 Binder 中的 static get 方法实例化 Binder 对象
        Object binderObject = getMethod.invoke(null, environment);

        // 通过执行实例化的 Binder 对象中 bind 方法获取到配置（根据 prefix 匹配）绑定结果(BindResult)
        Object bindResultObject = bindMethod.invoke(binderObject, prefix, targetClass);

        // 获取到 BindResult 中 get 方法的 Method 对象
        Method resultGetMethod = bindResultObject.getClass().getDeclaredMethod("get");
        // 通过调用 BindResult 中的 get 无参方法获取到配置列表
        return (T) resultGetMethod.invoke(bindResultObject);
    }
}